Failed Conditions
Push — master ( 711045...1671e4 )
by Yo
01:54
created

Alfred.js ➔ ???   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 12
Bugs 0 Features 0
Metric Value
cc 1
c 12
b 0
f 0
nc 1
nop 1
dl 0
loc 24
rs 8.9713

1 Function

Rating   Name   Duplication   Size   Complexity  
A Alfred.js ➔ ... ➔ ??? 0 19 2
1
"use strict";
2
3
const sarahClient = require('./client/sarahClient');
4
const pMapNormalizeAnyway = require('./plugins/promise/mapNormalizeAnyway');
5
const pAllAnyway = require('./plugins/promise/allAnyway');
6
const logger = require('./logger');
7
const DecoratorPlugin = require('./model/DecoratorPlugin');
8
const NestedError = require('nested-error-stacks');
9
const ActorPlugin = require('./model/ActorPlugin');
10
11
12
/**
13
 * Represent the bot which can speak and listen to you
14
 */
15
class Alfred {
16
    /**
17
     * @public
18
     *
19
     * @param {string} textToSpeech
20
     *
21
     * @returns {Promise<null|Error>}
22
     */
23
    speak(textToSpeech) {
24
25
        return this.decorateTts(textToSpeech)
26
            .then(textToSpeech => {
27
                if (!textToSpeech) {
28
                    return Promise.resolve();
29
                }
30
31
                this.logger.debug(`Alfred will say "${textToSpeech}"`);
32
33
                return sarahClient({
34
                    tts: textToSpeech,
35
                    sync: true
36
                })
37
                    .then(() => null)
38
                    .catch(error => {
39
                        const newError = new NestedError('Alfred::speak error', error);
40
                        this.logError(newError);
41
42
                        return Promise.reject(newError);
43
                    });
44
            })
45
        ;
46
    }
47
48
    /**
49
     * @public
50
     *
51
     * @returns {Promise<null|Error>}
52
     */
53
    listen() {
54
        return sarahClient({'listen': true})
55
            .catch(error => {
56
                const newError = new NestedError('Alfred::listen error', error);
57
                this.logError(newError);
58
59
                const throwError = () => {
60
                    return Promise.reject(newError);
61
                };
62
63
                const tts = 'Impossible de vous écouter';
64
                return this.speak(tts)
65
                    .catch(error => {
0 ignored issues
show
Unused Code introduced by
The parameter error is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
66
                        logger.warning(`skip previous speak("${tts}") error !`);
67
68
                        return throwError();
69
                    })
70
                    .then(throwError);
71
            })
72
        ;
73
    }
74
75
    /**
76
     * @public
77
     *
78
     * @returns {Promise<null|Error>}
79
     */
80
    stopListening() {
81
        return sarahClient({'listen': false})
82
            .catch(error => {
83
                const newError = new NestedError('Alfred::stopListening error', error);
84
                this.logError(newError);
85
86
                const throwError = () => {
87
                    return Promise.reject(newError);
88
                };
89
                const tts = 'je n\'arrive pas à ne rien faire ! J\'ai besoin de votre aide !';
90
91
                return this.speak(tts)
92
                    .catch(error => {
0 ignored issues
show
Unused Code introduced by
The parameter error is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
93
                        logger.warning(`skip previous speak("${tts}") error !`);
94
95
                        return throwError();
96
                    })
97
                    .then(throwError);
98
            })
99
            .then(() => this.speak('A votre service !'))
100
        ;
101
    }
102
103
    /**
104
     * @public
105
     * Politely wake up Alfred before asking him something.
106
     *
107
     * He will wake up each of his leprechauns
108
     *
109
     * @return {Promise}
110
     */
111
    init() {
112
        let haveInvalidPlugins = false;
113
        return pAllAnyway(
114
            this.pluginList.map(plugin => plugin.init(this))
115
        )
116
            .then(({resolvedList, rejectedList}) => {
117
                haveInvalidPlugins = rejectedList.length > 0;
118
119
                return this.cleanPluginList(resolvedList.length ? resolvedList.keys() : []);
120
            })
121
            .then(() => this.splitPluginListByRole())
122
            .then(() => {this.initialized = true;})
123
            .then(() => {
124
                if (haveInvalidPlugins) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if haveInvalidPlugins is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
125
                    return this.speak('Certain lutins semble malades. Ils sont au repos pour le moment.');
126
                }
127
            })
128
            .then(() => this.listen())
129
            .then(() => this.speak('A votre écoute'))
130
        ;
131
    }
132
133
    /**
134
     * @public
135
     *
136
     * @param {Plugin[]} pluginList
137
     */
138
    constructor(pluginList = []) {
139
        this.logger = logger;
140
        /** @type {Plugin[]} */
141
        this.pluginList = pluginList;
142
        /** @type {DecoratorPlugin[]} */
143
        this.decoratorList = [];
144
        /** @type {ActorPlugin[]} */
145
        this.actorList = [];
146
        /** @type {Plugin[]} */
147
        this.invalidPluginList = [];
148
    }
149
150
    /**
151
     * @private
152
     *
153
     * @param {Promise<string>} textToSpeech
154
     */
155
    decorateTts(textToSpeech) {
156
        return pMapNormalizeAnyway(
157
            textToSpeech,
158
            this.decoratorList
159
                // Return a callback that accept the value to normalize
160
                .map(decorator => decorator.normalizeTts)
161
        )
162
    }
163
164
    /**
165
     * @private
166
     *
167
     * @return {Promise<undefined|Error>}
168
     */
169
    cleanPluginList(validPluginIdList) {
170
        // Override plugins list with only valid ones
171
        const backupPluginList = this.pluginList;
172
        this.pluginList = [];
173
174
        return new Promise((resolve, reject) => {
175
            try {
176
                this.pluginList = validPluginIdList.map(index => {
177
                    return backupPluginList[index];
178
                });
179
                resolve();
180
            } catch (e) {
181
                reject(new NestedError('Error during plugins validity split', error));
0 ignored issues
show
Bug introduced by
The variable error seems to be never declared. If this is a global, consider adding a /** global: error */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
182
            }
183
        });
184
    }
185
186
    /**
187
     * @private
188
     *
189
     * @return {Promise<undefined|Error>}
190
     */
191
    splitPluginListByRole() {
192
        return Promise.all([
193
            () => {
194
                this.decoratorList = this.pluginList
195
                    .map(plugin => plugin instanceof DecoratorPlugin);
196
            },
197
            () => {
198
                this.actorList = this.pluginList
199
                    .map(plugin => plugin instanceof ActorPlugin);
200
            }
201
        ])
202
            .catch(error => Promise.reject(new NestedError('Error during plugins roles split', error)));
203
    }
204
205
    /**
206
     * @private
207
     * @param {Error} error
208
     *
209
     * @returns {Error}
210
     */
211
    logError(error) {
212
        this.logger.error(`Alfred error => ${error.message}`);
213
214
        return error;
215
    }
216
}
217
218
module.exports = Alfred;
219